#include "meshTypeFunctions.hpp"
#include "structMesh_testFunctions.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>

using Catch::Approx;

#define MAKE_MESH(M,N,DX,DY) auto mesh { uniMeshAs<RectMesh>(M,N,DX,DY) };
#define MESH_PREFIX "Rect"
#define MESH_TYPE hms::RectMesh

/* standardised tests */
#include "structMeshTestHelper.hpp"

/* RectMesh specific tests */
TEST_CASE( "Non-uniform RectMesh: Number of edges in different regions" ){
	using hms::exactlyEqual;
	
	auto test = []( const auto& mesh ){
		REQUIRE( mesh.nInnerEdges()    == 17 );
		REQUIRE( mesh.nBoundaryEdges() == 14 );
		REQUIRE( mesh.nEdges()         == 31 );
	};

	SECTION("One ghost layer"){
		test(createRectMesh());
	}
}

TEST_CASE( "Non-uniform RectMesh: number of cells of different kinds" ){
	using hms::exactlyEqual;

	auto testOneLayer = [](const auto& mesh){
		REQUIRE( mesh.nDomainCells() ==  12 );
		REQUIRE( mesh.nGhostCells () ==  14 );
		REQUIRE( mesh.nCells      () ==  26 );
	};
	auto testTwoLayers = [](const auto& mesh){
		REQUIRE( mesh.nDomainCells() ==  12 );
		REQUIRE( mesh.nGhostCells () ==  28 );
		REQUIRE( mesh.nCells      () ==  40 );
	};

	SECTION("One ghost layer"){
		testOneLayer(createRectMesh());
	}
}

TEST_CASE( "Non-uniform RectMesh: vertices and area" ){
	using hms::approxEqual;
	
	auto test = []( const auto& mesh ){
		REQUIRE( mesh.nVertices() == 20 );
		REQUIRE( mesh.domainArea() ==  Approx(15) );

		REQUIRE( approxEqual(mesh.vertex( 0),  Vector2s{ 0.0, 0.0 } ) );
		REQUIRE( approxEqual(mesh.vertex( 1),  Vector2s{ 0.5, 0.0 } ) );
		REQUIRE( approxEqual(mesh.vertex( 2),  Vector2s{ 1.5, 0.0 } ) );
		REQUIRE( approxEqual(mesh.vertex( 3),  Vector2s{ 3.0, 0.0 } ) );
		REQUIRE( approxEqual(mesh.vertex( 4),  Vector2s{ 0.0, 1.5 } ) );
		REQUIRE( approxEqual(mesh.vertex( 5),  Vector2s{ 0.5, 1.5 } ) );
		REQUIRE( approxEqual(mesh.vertex( 6),  Vector2s{ 1.5, 1.5 } ) );
		REQUIRE( approxEqual(mesh.vertex( 7),  Vector2s{ 3.0, 1.5 } ) );
		REQUIRE( approxEqual(mesh.vertex( 8),  Vector2s{ 0.0, 2.0 } ) );
		REQUIRE( approxEqual(mesh.vertex( 9),  Vector2s{ 0.5, 2.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(10),  Vector2s{ 1.5, 2.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(11),  Vector2s{ 3.0, 2.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(12),  Vector2s{ 0.0, 3.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(13),  Vector2s{ 0.5, 3.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(14),  Vector2s{ 1.5, 3.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(15),  Vector2s{ 3.0, 3.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(16),  Vector2s{ 0.0, 5.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(17),  Vector2s{ 0.5, 5.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(18),  Vector2s{ 1.5, 5.0 } ) );
		REQUIRE( approxEqual(mesh.vertex(19),  Vector2s{ 3.0, 5.0 } ) );
	};

	SECTION("One ghost layer"){
		test(createRectMesh());
	}
}

TEST_CASE( "Non-uniform RectMesh: boundary patches" ){
	using hms::exactlyEqual;

	auto testOneLayer = [](auto& mesh){
		hms::addStandardBoundaryPatches(mesh);

		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").edges(),  Array3i{ 0,  1,  2    }) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).edges(),  Array3i{12, 13, 14    }) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).edges(),  Array4i{15, 19, 23, 27}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).edges(),  Array4i{18, 22, 26, 30}) );
		
		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").ghostCells(), Array3i{ 1, 2, 3} ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).ghostCells(), Array3i{26,27,28} ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).ghostCells(), Array4i{ 5,10,15,20} ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).ghostCells(), Array4i{ 9,14,19,24} ) );

		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").domainCells(), Array3i{ 6, 7, 8}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).domainCells(), Array3i{21,22,23}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).domainCells(), Array4i{ 6,11,16,21}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).domainCells(), Array4i{ 8,13,18,23}) );
	};

	auto testTwoLayers = [](auto& mesh){
		hms::addStandardBoundaryPatches(mesh);

		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").edges(),  Array3i{ 0,  1,  2    }) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).edges(),  Array3i{12, 13, 14    }) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).edges(),  Array4i{15, 19, 23, 27}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).edges(),  Array4i{18, 22, 26, 30}) );
		
		ArrayXXi bottom (2,3); bottom <<  9, 10, 11,  2,  3,  4;
		ArrayXXi top    (2,3); top    << 44, 45, 46, 51, 52, 53;
		ArrayXXi left   (2,4); left   << 15, 22, 29, 36, 14, 21, 28, 35;
		ArrayXXi right  (2,4); right  << 19, 26, 33, 40, 20, 27, 34, 41;
		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").ghostCells(), bottom) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).ghostCells(), top   ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).ghostCells(), left  ) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).ghostCells(), right ) );

		REQUIRE(exactlyEqual(mesh.boundaryPatch("bottom").domainCells(), Array3i{16,17,18}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("top"   ).domainCells(), Array3i{37,38,39}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("left"  ).domainCells(), Array4i{16,23,30,37}) );
		REQUIRE(exactlyEqual(mesh.boundaryPatch("right" ).domainCells(), Array4i{18,25,32,39}) );
	};

	SECTION("One ghost layer"){
		hms::RectMesh mesh = createRectMesh();
		testOneLayer(mesh);
	}
}

TEST_CASE( "Non-uniform RectMesh: x-coordinates" ){
	using hms::approxEqual;

	auto testOneLayer = [](const auto& mesh){
		ArrayXs xcoords(6);
		xcoords << -0.5, 0.0, 0.5, 1.5, 3.0, 4.5;
		REQUIRE( approxEqual( mesh.xCoords(), xcoords ) ); 
	};

	auto testTwoLayers = [](const auto& mesh){
		ArrayXs xcoords(8);
		xcoords << -1.0, -0.5, 0.0, 0.5, 1.5, 3.0, 4.5, 6.0;
		REQUIRE( approxEqual( mesh.xCoords(), xcoords ) ); 
	};

	SECTION("One ghost layer"){
		testOneLayer(createRectMesh());
	}
}

TEST_CASE( "Non-uniform RectMesh: y-coordinates" ){
	using hms::approxEqual;

	auto testOneLayer = [](const auto& mesh){
		ArrayXs ycoords(7);
		ycoords << -1.5, 0.0, 1.5, 2.0, 3.0, 5.0, 7.0;
		REQUIRE( approxEqual( mesh.yCoords(), ycoords ) ); 
	};

	auto testTwoLayers = [](const auto& mesh){
		ArrayXs ycoords(9);
		ycoords << -3.0, -1.5, 0.0, 1.5, 2.0, 3.0, 5.0, 7.0, 9.0;
		REQUIRE( approxEqual( mesh.yCoords(), ycoords ) ); 
	};

	SECTION("One ghost layer"){
		testOneLayer(createRectMesh());
	}
}

TEST_CASE( "Non-uniform RectMesh: inner x-coordinates" ){
	using hms::approxEqual;

	auto testOneLayer = [](const auto& mesh){
		ArrayXs xcoords(4);
		xcoords << 0.0, 0.5, 1.5, 3.0;
		REQUIRE( approxEqual( mesh.xCoordsInner(), xcoords ) ); 
	};

	auto testTwoLayers = [](const auto& mesh){
		ArrayXs xcoords(4);
		xcoords << 0.0, 0.5, 1.5, 3.0;
		REQUIRE( approxEqual( mesh.xCoordsInner(), xcoords ) ); 
	};

	SECTION("One ghost layer"){
		testOneLayer(createRectMesh());
	}
}

TEST_CASE( "Non-uniform RectMesh: inner y-coordinates" ){
	using hms::approxEqual;

	auto testOneLayer = [](const auto& mesh){
		ArrayXs ycoords(5);
		ycoords << 0.0, 1.5, 2.0, 3.0, 5.0;
		REQUIRE( approxEqual( mesh.yCoordsInner(), ycoords ) ); 
	};

	auto testTwoLayers = [](const auto& mesh){
		ArrayXs ycoords(5);
		ycoords << 0.0, 1.5, 2.0, 3.0, 5.0;
		REQUIRE( approxEqual( mesh.yCoordsInner(), ycoords ) ); 
	};

	SECTION("One ghost layer"){
		testOneLayer(createRectMesh());
	}
}

TEST_CASE( "RectMesh: dx and dy" ){

	auto testOneLayer = [](const auto& mesh){
		ArrayXs dx(5), dy(6);
		dx << 0.5, 0.5, 1.0, 1.5, 1.5;
		dy << 1.5, 1.5, 0.5, 1.0, 2.0, 2.0;
		REQUIRE( approxEqual( mesh.dxInner(), dx.segment(1,3) ) );
		REQUIRE( approxEqual( mesh.dx(), dx ) );
		REQUIRE( approxEqual( mesh.dyInner(), dy.segment(1,4) ) );
		REQUIRE( approxEqual( mesh.dy(), dy ) );
	};

	auto testTwoLayers = [](const auto& mesh){
		ArrayXs dx(7), dy(8);
		dx << 0.5, 0.5, 0.5, 1.0, 1.5, 1.5, 1.5;
		dy << 1.5, 1.5, 1.5, 0.5, 1.0, 2.0, 2.0, 2.0;
		REQUIRE( approxEqual( mesh.dxInner(), dx.segment(2,3) ) );
		REQUIRE( approxEqual( mesh.dx(), dx ) );
		REQUIRE( approxEqual( mesh.dyInner(), dy.segment(2,4) ) );
		REQUIRE( approxEqual( mesh.dy(), dy ) );
	};

	SECTION("One ghost layer"){
		testOneLayer( createRectMesh() );
	}
}